<template>
  <div class="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 py-8">
    <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
      <div class="text-center mb-6">
        <h1 class="text-3xl font-bold text-gray-900 mb-2">猫咪跑酷 — 未来城市</h1>
        <p class="text-sm text-gray-600">控制你的猫咪跳跃躲避障碍，尽可能跑得更远。按 空格 / ↑ 跳跃，或触摸/点击屏幕（移动端友好）。</p>
      </div>

      <div class="flex flex-col lg:flex-row gap-6">
        <main class="flex-1 bg-white/80 rounded-xl shadow p-4">
          <div class="flex justify-between items-center mb-3">
            <div class="text-sm text-gray-700">得分: <span class="font-mono">{{ score }}</span></div>
            <div class="text-sm text-gray-700">最高分: <span class="font-mono">{{ highScore }}</span></div>
          </div>

          <div class="w-full h-72 md:h-96 relative">
            <canvas ref="canvas" class="w-full h-full rounded-md bg-gradient-to-b from-slate-900 via-indigo-900 to-purple-900"></canvas>

            <div v-if="gameOver" class="absolute inset-0 flex items-center justify-center">
              <div class="bg-white/90 rounded-lg p-6 text-center shadow max-w-sm w-full">
                <h2 class="text-2xl font-bold mb-2">游戏结束</h2>
                <p class="mb-4">得分：<span class="font-mono">{{ score }}</span></p>

                <div class="flex flex-col sm:flex-row items-center justify-center gap-3">
                  <button @click="restart" class="px-4 py-2 bg-indigo-600 text-white rounded shadow">重新开始</button>

                  <div class="flex items-center gap-2">
                    <input v-model="name" placeholder="你的名字（可选）" class="border rounded px-2 py-1" />
                    <button :disabled="score===0" @click="submitScore" class="px-3 py-1 bg-green-500 text-white rounded disabled:opacity-60">保存成绩</button>
                  </div>
                </div>
              </div>
            </div>
          </div>

          <div class="mt-3 text-xs text-gray-500">提示：长按或连续跳跃无法飞，掌握时机才能得高分。</div>
        </main>

        <aside class="w-full lg:w-80 bg-white p-4 rounded-xl shadow max-h-[520px] overflow-hidden">
          <h2 class="text-lg font-semibold mb-3">排行榜（最近 7 天）</h2>
          <div class="text-xs text-gray-500 mb-2">按得分降序（越大越好）。若列表很多可滚动查看。</div>

          <div class="overflow-y-auto" style="max-height:420px">
            <ul>
              <li v-for="(item, idx) in leaderboard" :key="item.id" class="py-2 px-2 border-b last:border-b-0 flex items-center justify-between">
                <div class="flex items-center gap-3">
                  <div class="w-8 text-center">{{ idx + 1 }}</div>
                  <div>
                    <div class="font-medium">{{ item.name || '匿名' }}</div>
                    <div class="text-xs text-gray-500">{{ new Date(item.ts).toLocaleString() }}</div>
                  </div>
                </div>
                <div class="text-right">
                  <div class="font-semibold">{{ item.score }}</div>
                  <div v-if="idx===0" class="text-xs text-red-500">最高</div>
                </div>
              </li>
            </ul>
            <div v-if="leaderboard.length===0" class="text-center text-gray-400 py-8">暂无成绩，快来挑战！</div>
          </div>
        </aside>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

const canvas = ref(null)
const score = ref(0)
// Don't access localStorage at module/eval time (SSR). Initialize to 0 and hydrate onMounted.
const highScore = ref(0)
const gameOver = ref(false)

// leaderboard state (frontend only, mirrors hand-speed.vue)
const name = ref('')
const leaderboard = ref([])
const isTop = ref(false)

let ctx = null
let rafId = null
let width = 0
let height = 0

const cat = {
  x: 80,
  y: 0,
  w: 40,
  h: 32,
  vy: 0,
  gravity: 0.9,
  jumpPower: -14,
}

let groundY = 0
let obstacles = []
let obstacleTimer = 0
let obstacleInterval = 90 // frames
let speed = 4

let bgLayers = []

function resetGame() {
  score.value = 0
  obstacles = []
  obstacleTimer = 0
  speed = 4
  // reset obstacle interval to initial value so restarting does not immediately spawn many obstacles
  obstacleInterval = 90
  cat.y = groundY - cat.h
  cat.vy = 0
  gameOver.value = false
}

function spawnObstacle() {
  const h = 24 + Math.random() * 36
  obstacles.push({ x: width + 20, y: groundY - h, w: 22 + Math.random() * 24, h, passed: false })
}

function createBg() {
  bgLayers = [
    { speed: 0.2, color: '#0b1226', buildings: [] },
    { speed: 0.5, color: '#111827', buildings: [] },
    { speed: 1.0, color: '#1f2937', buildings: [] },
  ]
  bgLayers.forEach(layer => {
    layer.buildings = []
    let x = 0
    while (x < width * 2) {
      const bw = 40 + Math.random() * 120
      const bh = (height * 0.2) + Math.random() * (height * 0.45)
      layer.buildings.push({ x, w: bw, h: bh })
      x += bw + (20 + Math.random() * 80)
    }
  })
}

function drawBackground() {
  // star-like glows
  ctx.fillStyle = 'rgba(255,255,255,0.03)'
  ctx.fillRect(0, 0, width, height)

  bgLayers.forEach((layer, li) => {
    ctx.fillStyle = layer.color
    layer.buildings.forEach(b => {
      const bx = (b.x - (performance.now() / 30) * layer.speed) % (width * 2)
      const drawX = bx < 0 ? bx + width * 2 : bx
      const by = groundY - b.h
      ctx.fillRect(drawX - width, by, b.w, b.h)
      ctx.fillRect(drawX, by, b.w, b.h)
    })
  })

  // ground
  ctx.fillStyle = '#0f1724'
  ctx.fillRect(0, groundY, width, height - groundY)

  // neon strip
  const grad = ctx.createLinearGradient(0, groundY - 8, 0, groundY + 8)
  grad.addColorStop(0, 'rgba(99,102,241,0.7)')
  grad.addColorStop(1, 'rgba(139,92,246,0.7)')
  ctx.fillStyle = grad
  ctx.fillRect(0, groundY - 6, width, 4)
}

function drawCat() {
  const x = cat.x
  const y = cat.y
  // body
  ctx.fillStyle = '#ffd166'
  ctx.beginPath()
  ctx.ellipse(x + cat.w / 2, y + cat.h / 2, cat.w / 2, cat.h / 2, 0, 0, Math.PI * 2)
  ctx.fill()

  // ears
  ctx.fillStyle = '#f4ac45'
  ctx.beginPath()
  ctx.moveTo(x + 6, y + 6)
  ctx.lineTo(x + 14, y - 6)
  ctx.lineTo(x + 22, y + 6)
  ctx.fill()

  ctx.beginPath()
  ctx.moveTo(x + cat.w - 6, y + 6)
  ctx.lineTo(x + cat.w - 14, y - 6)
  ctx.lineTo(x + cat.w - 22, y + 6)
  ctx.fill()

  // eyes
  ctx.fillStyle = '#111827'
  ctx.beginPath()
  ctx.arc(x + 14, y + 14, 3, 0, Math.PI * 2)
  ctx.fill()
  ctx.beginPath()
  ctx.arc(x + 26, y + 14, 3, 0, Math.PI * 2)
  ctx.fill()
}

function drawObstacles() {
  ctx.fillStyle = '#e11d48'
  obstacles.forEach(o => {
    ctx.fillRect(o.x, o.y, o.w, o.h)
    // neon top
    ctx.fillStyle = 'rgba(255,255,255,0.06)'
    ctx.fillRect(o.x, o.y, o.w, 4)
    ctx.fillStyle = '#e11d48'
  })
}

function update() {
  // physics
  cat.vy += cat.gravity
  cat.y += cat.vy
  if (cat.y + cat.h > groundY) {
    cat.y = groundY - cat.h
    cat.vy = 0
  }

  // obstacles
  obstacleTimer++
  if (obstacleTimer > obstacleInterval) {
    spawnObstacle()
    obstacleTimer = 0
    // gradually speed up and shorten interval
    if (obstacleInterval > 50) obstacleInterval -= 1
    speed += 0.02
  }

  for (let i = obstacles.length - 1; i >= 0; i--) {
    const o = obstacles[i]
    o.x -= speed
    if (!o.passed && o.x + o.w < cat.x) {
      o.passed = true
      score.value += 1
      if (score.value > highScore.value) {
        highScore.value = score.value
        try {
          if (typeof window !== 'undefined' && window.localStorage) {
            window.localStorage.setItem('cat-runner-high', String(highScore.value))
          }
        } catch (e) {
          // ignore storage write errors
        }
      }
    }
    if (o.x + o.w < -50) obstacles.splice(i, 1)
  }

  // collision
  for (const o of obstacles) {
    if (cat.x < o.x + o.w && cat.x + cat.w > o.x && cat.y + cat.h > o.y) {
      // collision
      gameOver.value = true
      cancelAnimationFrame(rafId)
      rafId = null
    }
  }
}

function loop() {
  if (!ctx) return
  ctx.clearRect(0, 0, width, height)
  drawBackground()
  drawCat()
  drawObstacles()
  if (!gameOver.value) {
    update()
    rafId = requestAnimationFrame(loop)
  }
}

function jump() {
  if (gameOver.value) return
  if (cat.y + cat.h >= groundY - 1) {
    cat.vy = cat.jumpPower
  }
}

function restart() {
  // fully reset game state and restart the render loop
  resetGame()
  // ensure any previous RAF is cancelled
  try {
    if (rafId) cancelAnimationFrame(rafId)
  } catch (e) {
    // ignore
  }
  rafId = requestAnimationFrame(loop)
}

// leaderboard functions
const fetchLeaderboard = async (limit = 50) => {
  try {
    const res = await $fetch('/api/cat-runner/list?limit=' + limit)
    if (Array.isArray(res)) {
      // sort by score desc
      leaderboard.value = res.map((r) => ({ ...r })).sort((a, b) => Number(b.score) - Number(a.score))
    }
  } catch (err) {
    // ignore errors for now
  }
  updateTopFlag()
}

const submitScore = async () => {
  try {
    await $fetch('/api/cat-runner/submit', { method: 'POST', body: { name: name.value || '匿名', score: score.value } })
    await fetchLeaderboard(50)
    alert('成绩已保存到排行榜')
  } catch (err) {
    alert('保存失败: ' + String(err))
  }
}

const updateTopFlag = () => {
  const best = leaderboard.value[0]
  if (!best || score.value == null) {
    isTop.value = true
    return
  }
  // lower interval is better in hand-speed; here higher score is better
  isTop.value = score.value >= Number(best.score)
}

onMounted(() => {
  const el = canvas.value
  if (!el) return
  ctx = el.getContext('2d')

  function resize() {
    width = Math.floor(el.clientWidth)
    height = Math.floor(el.clientHeight)
    el.width = width
    el.height = height
    groundY = Math.floor(height * 0.78)
    cat.y = groundY - cat.h
    createBg()
  }

  resize()
  window.addEventListener('resize', resize)

  // hydrate high score from localStorage (client only)
  try {
    if (typeof window !== 'undefined' && window.localStorage) {
      const v = window.localStorage.getItem('cat-runner-high')
      highScore.value = Number(v || 0)
    }
  } catch (e) {
    highScore.value = 0
  }

  // input
  function onKey(e) {
    if (e.code === 'Space' || e.code === 'ArrowUp' || e.key === 'w') {
      e.preventDefault()
      jump()
    }
  }
  function onClick() { jump() }

  window.addEventListener('keydown', onKey)
  el.addEventListener('mousedown', onClick)
  el.addEventListener('touchstart', onClick, { passive: true })

  // fetch leaderboard initially
  fetchLeaderboard(50)

  resetGame()
  rafId = requestAnimationFrame(loop)

  onBeforeUnmount(() => {
    window.removeEventListener('resize', resize)
    window.removeEventListener('keydown', onKey)
    el.removeEventListener('mousedown', onClick)
    el.removeEventListener('touchstart', onClick)
    if (rafId) cancelAnimationFrame(rafId)
  })
})

useHead({
  title: '猫咪跑酷 - 游戏工具 - 八八在线工具'
})
</script>


